package auth

import (
	"com.lh.basic.service/lib/crypto"
	"com.lh.basic.service/lib/files"
	"com.lh.basic.service/lib/tools"
	"com.lh.basic.service/pb/auth"
	"com.lh.data.data/lib/cockroach"
	"com.lh.data.data/lib/redis"
	"com.lh.data.data/pb/database"
	"com.lh.mail.service/pb/mailbox"
	"com.lh.user.service/pb/user"
	"context"
	"errors"
	"fmt"
	"github.com/gogo/protobuf/proto"
	"github.com/jackc/pgx/v5"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/durationpb"
	"strings"
	"time"
)

// 获取用户标识
// 返回sid和uuid
func (a *Auth) GetSid(ctx context.Context, req *auth.EmptyRequest) (*auth.SidResponse, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	db := redis.Init()
	var unix, id string
	var err error
	// 判断数据是否存在，存在更新过期时间
	if ok && len(md.Get("sid")) > 0 && len(md.Get("uuid")) > 0 {
		unix = md.Get("sid")[0]
		id = md.Get("uuid")[0]
		key := fmt.Sprintf("auth_%s", unix)
		_, err = db.GetRow(key)
		if err == nil {
			err = db.SetEX(key, 3*time.Hour)
		}
	}
	if len(unix) < 1 || len(id) < 1 {
		// 不存在创建用户随即sid和uuid
		cry := crypto.Init()
		unix = cry.RangeUnix()
		id = cry.Id.String()
		list := user.UserInfoData{Sid: unix, Uuid: cry.Id.String()}
		data, err := proto.Marshal(&list)
		if err != nil {
			return nil, err
		}
		err = db.SetRow(&database.MsgRequest{
			Key:      fmt.Sprintf("auth_%s", unix),
			Data:     data,
			Duration: durationpb.New(3 * time.Hour),
		})
		if err != nil {
			return nil, err
		}
	}
	return &auth.SidResponse{
		Code: 200,
		Data: &auth.SidResponse_Data{
			Sid:  unix,
			Uuid: id,
		},
	}, nil
}

func (a *Auth) PasswordStatus(pw string, info *auth.UserResponse, token *string) *auth.UserInfoResponse {
	stat := uint32(2)
	cry := crypto.Init()
	if info.Status == &stat && crypto.Verify(pw, info.Password) {
		return &auth.UserInfoResponse{
			Code:     200,
			Userinfo: info,
			Token:    *token,
			Message:  "初始化账户",
			Status:   "warning",
		}
	} else if cry.PWVerify(pw, info.Password, info.Sign) {
		return &auth.UserInfoResponse{
			Code:     200,
			Userinfo: info,
			Token:    *token,
			Message:  "登陆成功",
			Status:   "ok",
		}
	} else {
		return &auth.UserInfoResponse{
			Code:     406,
			Userinfo: nil,
			Message:  "用户名或者密码错误",
			Status:   "error",
		}
	}
}

func (a *Auth) CodeStatus(key string, info *auth.UserResponse, token *string) *auth.UserInfoResponse {
	db := redis.Init()
	if code, err := db.GetHRow(fmt.Sprintf("code_%s", key), "code"); err == nil {
		if code == info.Code {
			return &auth.UserInfoResponse{
				Code:     200,
				Userinfo: info,
				Token:    *token,
				Message:  "登陆成功",
				Status:   "warning",
			}
		} else {
			return &auth.UserInfoResponse{
				Code:     406,
				Userinfo: nil,
				Message:  "验证码错误，请输入正确验证码",
				Status:   "error",
			}
		}
	} else {
		return &auth.UserInfoResponse{
			Code:     406,
			Userinfo: nil,
			Message:  "操作异常，请重试",
			Status:   "error",
		}
	}
}

// 用户登陆返回用户信息和token
// 传值参数
// username 必传
// password 密码情况下必传
// code 验证码情况下必传
// duration 过期时间
// protocol 是否同意用户协议
// type 登陆类型
func (a *Auth) Login(ctx context.Context, req *auth.LoginRequest) (*auth.UserInfoResponse, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}
	db, err := cockroach.GetDB(ctx)
	pw := req.GetPassword()
	if err != nil {
		return nil, err
	}
	defer db.Pool.Close()
	key, val := tools.IsAccount(req.GetUsername())
	userInfo := &auth.UserResponse{}
	list := db.GetEmptyList(userInfo)
	sql := fmt.Sprintf("select %s from users.list where %s = $1;", strings.Join(list, ", "), key)
	rows, err := db.Pool.Query(context.Background(), sql, val)
	if err != nil {
		return nil, err
	}
	info, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[auth.UserResponse])
	if err != nil {
		return nil, err
	}

	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, status.Errorf(codes.NotFound, "请刷新浏览器，获取sid")
	}
	sid := md.Get("sid")
	var token *string
	if len(sid) > 0 {
		duration := durationpb.New(time.Hour * 12)
		if req.Duration.IsValid() {
			duration = req.Duration
		}
		token, err = tools.GetToken(&auth.GTokenConfig{
			Sid:      sid[0],
			Duration: duration,
		})
		if err != nil {
			return nil, status.Errorf(codes.NotFound, "生成token失败，请重试")
		}
	}
	typ := req.GetType()
	if typ == 1 {
		// 密码登陆
		return a.PasswordStatus(pw, &info, token), err
	} else if typ == 2 {
		// 验证码登陆
		return a.CodeStatus(sid[0], &info, token), err
	} else {
		return &auth.UserInfoResponse{
			Code:     406,
			Userinfo: nil,
			Message:  "登录类型不受支持",
			Status:   "error",
		}, err
	}
}

func (a *Auth) GetCode(ctx context.Context, req *auth.CodeRequest) (*auth.NoResponse, error) {
	cry := crypto.Init()
	code := cry.GenerateCode(4, 8)
	conn, err := tools.Proxy("mail")
	if err != nil {
		val := "验证码发送失败"
		return &auth.NoResponse{
			Code:    417,
			Data:    &val,
			Message: "成功",
			Status:  "ok",
		}, nil
	}
	mail := mailbox.NewMailboxServiceClient(conn)
	file := files.Init()
	cos := auth.CodeInfo{
		Title:    req.GetTitle(),
		Duration: "5分钟",
		Code:     code,
	}
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, errors.New("获取header信息失败")
	}
	sid := md.Get("sid")
	if len(sid) == 0 {
		return nil, errors.New("请刷新浏览器再试")
	}
	red := redis.Init()
	if err = red.SetHRow(redis.HConfig{
		Key:      fmt.Sprintf("code_%s:1", sid[0]),
		Duration: time.Minute * 5,
		HList:    []interface{}{"code", code, "isType", req.GetIsType()},
	}); err != nil {
		return nil, err
	}

	html, err := file.GetMailHtml("com.lh.basic.service", "mail_code.html", map[string]string{
		"title":    cos.Title,
		"duration": cos.Duration,
		"code":     code,
	})
	if err != nil {
		return nil, err
	}
	data := mailbox.SendRequest{
		Sender:    "allkic@163.com",
		Recipient: []string{req.GetEmail()},
		Subject:   cos.Title,
		Body:      &html,
		IsHtml:    true,
	}
	mails, err := mail.SendMail(ctx, &data)
	if err != nil {
		return nil, err
	}
	return &auth.NoResponse{
		Code:    mails.Code,
		Data:    mails.Data,
		Message: mails.Message,
		Status:  mails.Status,
	}, err
}
